﻿#include "hikvision.h"
#include "../functions/functions.h"

HikCamera::HikCamera()
{
    this->camera = (CameraInfo*)malloc(sizeof(CameraInfo));
    if (this->camera == NULL) { return; }
    memset(this->camera, 0, sizeof(CameraInfo));
}

HikCamera::~HikCamera()
{
}

/// <summary>
/// 初始化摄像机参数
/// </summary>
/// <param name="ip"></param>
/// <param name="port"></param>
/// <param name="username"></param>
/// <param name="password"></param>
/// <returns></returns>
int HikCamera::init(std::string ip, int port, std::string username, std::string passowrd)
{
    if (this->camera == NULL)
    {
        this->camera = (CameraInfo*)malloc(sizeof(CameraInfo));
        if (this->camera == NULL) { return -1; }
        memset(this->camera, 0, sizeof(CameraInfo));
    }

    this->camera->port = port;
    memcpy(this->camera->ip, ip.c_str(), ip.length() > 20 ? 20 : ip.length());
    memcpy(this->camera->username, username.c_str(), username.length() > 20 ? 20 : username.length());
    memcpy(this->camera->password, passowrd.c_str(), passowrd.length() > 32 ? 32 : passowrd.length());
    this->camera->isSuccess = 1;

    return 0;
}


/// <summary>
/// 是否初始化摄像机
/// </summary>
/// <returns></returns>
bool HikCamera::isInit()
{
    if (this->camera == NULL || this->camera->isSuccess == false)
    {
        return false;
    }

    return true;
}


/******************************************************************************
 *  Name        :   HikCamera::devInfo
 *  Author      :   cqnews
 *  Version     :   V1.0.0
 *  Data        :   2022.07.20
 *  Describe    :   获取设备信息 /ISAPI/System/deviceInfo
 ******************************************************************************/
int HikCamera::devInfo()
{
    try
    {

        if (!isInit())
        {
            return -1;
        }

        char protocolUrl[] = "/ISAPI/System/deviceInfo";
        //char protocolUrl[] = "/ISAPI/Streaming/channels/1/picture";

        char authorizationBuf[HTTP_DEGIST_SRC_LEN] = { 0 };
        int ret = HikCamera::getInstance()->generateAuthorization(IsapiProtocolCommand::ISAPI_GET,
            protocolUrl,
            authorizationBuf);

        if (ret != 0) //签名失败
        {
            return -1;
        }

        CURLcode res;
        CURL* curl; //curl库
        struct curl_slist* headers = NULL;

        if (initCurl(&curl, headers) != 0)
        {
            clearCurl(&curl, headers);
            return -2;
        }

        headers = curl_slist_append(headers, authorizationBuf);

        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);// 改协议头
        std::string cameraUrl = "http://";
        cameraUrl.append(this->camera->ip);
        cameraUrl.append(":");
        cameraUrl.append(std::to_string(this->camera->port));
        cameraUrl.append(protocolUrl);

#ifdef DEBUG
        std::cout << "cameraurl:" << cameraUrl << std::endl;
#endif // DEBUG

        std::string body = "";
        curl_easy_setopt(curl, CURLOPT_URL, cameraUrl.c_str());
        curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "GET");

        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, HikCamera::readStringBuffFunction);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &body);

        res = curl_easy_perform(curl); //执行
        clearCurl(&curl, headers);

        if (res != CURLE_OK)
        {
            fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
            return -1;
        }


        //TODO
        std::cout << "body:" << body << std::endl;


        return 0;
    }
    catch (...)
    {
        return -1;
    }
}

/******************************************************************************
 *  Name        :   HikCamera::devStatus
 *  Author      :   cqnews
 *  Version     :   V1.0.0
 *  Data        :   2022.07.22
 *  Describe    :   获取设备状态 /ISAPI/System/status
 ******************************************************************************/
int HikCamera::devStatus()
{
    try
    {

        if (!isInit())
        {
            return -1;
        }

        char protocolUrl[] = "/ISAPI/System/status";
        //char protocolUrl[] = "/ISAPI/Streaming/channels/1/picture";

        char authorizationBuf[HTTP_DEGIST_SRC_LEN] = { 0 };
        int ret = HikCamera::getInstance()->generateAuthorization(IsapiProtocolCommand::ISAPI_GET,
            protocolUrl,
            authorizationBuf);

        if (ret != 0) //签名失败
        {
            return -1;
        }

        CURLcode res;
        CURL* curl; //curl库
        struct curl_slist* headers = NULL;

        if (initCurl(&curl, headers) != 0)
        {
            clearCurl(&curl, headers);
            return -2;
        }

        headers = curl_slist_append(headers, authorizationBuf);

        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);// 改协议头
        std::string cameraUrl = "http://";
        cameraUrl.append(this->camera->ip);
        cameraUrl.append(":");
        cameraUrl.append(std::to_string(this->camera->port));
        cameraUrl.append(protocolUrl);

#ifdef DEBUG
        std::cout << "cameraurl:" << cameraUrl << std::endl;
#endif // DEBUG

        std::string body = "";
        curl_easy_setopt(curl, CURLOPT_URL, cameraUrl.c_str());
        curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "GET");

        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, HikCamera::readStringBuffFunction);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &body);

        res = curl_easy_perform(curl); //执行
        clearCurl(&curl, headers);

        if (res != CURLE_OK)
        {
            fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
            return -1;
        }


        //TODO
        std::cout << "body:" << body << std::endl;


        return 0;
    }
    catch (...)
    {
        return -1;
    }
}
/******************************************************************************
 *  Name        :   HikCamera::putCapture
 *  Author      :   cqnews
 *  Version     :   V1.0.0
 *  Data        :   2020.09.21
 *  Describe    :   摄像机抓图
 ******************************************************************************/
int HikCamera::putCapture(int channelId, const char* path)
{

    try
    {

        if (!isInit())
        {
            return -1;
        }

        char protocolUrl[] = "/ISAPI/Streaming/channels/1/picture";

        char authorizationBuf[HTTP_DEGIST_SRC_LEN] = { 0 };
        int ret = HikCamera::getInstance()->generateAuthorization(IsapiProtocolCommand::ISAPI_GET,
            protocolUrl,
            authorizationBuf);

        if (ret != 0) //签名失败
        {
            return -1;
        }

        FILE* fp;

        if ((fp = fopen(path, "w")) == NULL) //返回结果用文件存储
        {
            return -1;
        }

        CURLcode res;
        CURL* curl; //curl库
        struct curl_slist* headers = NULL;

        if (initCurl(&curl, headers) != 0)
        {
            clearCurl(&curl, headers);
            return -2;
        }


        std::string cameraUrl = "http://";
        cameraUrl.append(this->camera->ip);
        cameraUrl.append(":");
        cameraUrl.append(std::to_string(this->camera->port));
        cameraUrl.append(protocolUrl);


        std::cout << "cameraurl:" << cameraUrl << std::endl;
#ifdef DEBUG
#endif // DEBUG

        headers = curl_slist_append(headers, authorizationBuf);
        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);// 改协议头
        curl_easy_setopt(curl, CURLOPT_URL, cameraUrl.c_str()); //"http://admin:hik123456@192.168.1.67/ISAPI/Streaming/channels/1/picture"
        curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "GET");
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
        curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);

        res = curl_easy_perform(curl); //执行

        if (res != CURLE_OK)
        {
            fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
        }

        fclose(fp);
        clearCurl(&curl, headers);
        return 0;
    }
    catch (...)
    {
    }

    return -1;
}

/******************************************************************************
 *  Name        :   HikCamera::getRtspUrl
 *  Author      :   cqnews
 *  Version     :   V1.0.0
 *  Data        :   2020.09.21
 *  Describe    :   获取摄像机参数信息
 *  rtsp://admin:hik123456@192.168.1.99:554/mpeg4/ch36/main/av_stream
 ******************************************************************************/
std::string HikCamera::getRtspUrl()
{
    std::string cameraUrl = "rtsp://";

    cameraUrl.append(this->camera->username);
    cameraUrl.append(":");
    cameraUrl.append(this->camera->password);
    cameraUrl.append("@");
    cameraUrl.append(this->camera->ip);
    cameraUrl.append("/mpeg4/ch36/main/av_stream");

    return cameraUrl;
}

/******************************************************************************
 *  Name        :   HikCamera::putControl
 *  Author      :   cqnews
 *  Version     :   V1.0.0
 *  Data        :   2020.09.21
 *  Describe    :   摄像机控制旋转
 *  rtsp://admin:hik123456@192.168.1.99:554/mpeg4/ch36/main/av_stream
 ******************************************************************************/
int HikCamera::putControl(int channelId, int pan, int tilt, int zoom)
{
    if (!isInit())
    {
        return -1;
    }

    char protocolUrl[] = "/ISAPI/PTZCtrl/channels/1/continuous";
    //char protocolUrl[] = "/ISAPI/Streaming/channels/1/picture";

    char authorizationBuf[HTTP_DEGIST_SRC_LEN] = { 0 };
    int ret = HikCamera::getInstance()->generateAuthorization(IsapiProtocolCommand::ISAPI_GET,
        protocolUrl,
        authorizationBuf);

    if (ret != 0) //签名失败
    {
        return -1;
    }


    CURLcode res;
    CURL* curl; //curl库
    struct curl_slist* headers = NULL;

    if (initCurl(&curl, headers) != 0)
    {
        clearCurl(&curl, headers);
        return -2;
    }


    std::string cameraUrl = "http://";

    cameraUrl.append(this->camera->ip);
    cameraUrl.append(":");
    cameraUrl.append(std::to_string(this->camera->port));
    cameraUrl.append(protocolUrl);

    std::cout << cameraUrl << std::endl;

    std::string data = "<?xml version:\"1.0\" encoding=\"UTF - 8\"?><PTZData>";

    data.append("<pan>");
    data.append(std::to_string(pan));
    data.append("</pan>");
    data.append("<tilt>");
    data.append(std::to_string(tilt));
    data.append("</tilt>");
    data.append("<zoom>");
    data.append(std::to_string(zoom));
    data.append("</zoom>");
    data.append("</PTZData>");

#ifdef DEBUG
    std::cout << "cameraurl:" << cameraUrl << std::endl;
#endif // DEBUG

    headers = curl_slist_append(headers, authorizationBuf);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);// 改协议头
    curl_easy_setopt(curl, CURLOPT_URL, cameraUrl.c_str()); //"http://admin:hik123456@192.168.1.67:80/ISAPI/PTZCtrl/channels/1/continuous"
    curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "GET");//根据要求，要用PUT方法，但为什么下面的POSTFIELDS会起作用，暂时还没清楚。记得libcurl的文档里说，POSTFIELDS所调用的是POST方法。。。待续。。。。
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str());

    curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, data.length());

    res = curl_easy_perform(curl);

    clearCurl(&curl, headers);
    if (res != CURLE_OK)
    {
        fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
    }


    return 0;
}

/******************************************************************************
 *  Name        :   HikCamera::reboot
 *  Author      :   cqnews
 *  Version     :   V1.0.0
 *  Data        :   2020.09.21
 *  Describe    :   摄像机重启
 *  http://admin:hik123456@192.168.1.99:554/ISAPI/System/reboot
 ******************************************************************************/
int HikCamera::reboot()
{
    if (!isInit())
    {
        return -1;
    }

    CURLcode res;
    CURL* curl; //curl库
    struct curl_slist* headers = NULL;

    if (initCurl(&curl, headers) != 0)
    {
        curl_slist_free_all(headers);
        curl_easy_cleanup(curl);
        return -2;
    }

    std::string cameraUrl = "http://";
    cameraUrl.append(this->camera->username);
    cameraUrl.append(":");
    cameraUrl.append(this->camera->password);
    cameraUrl.append("@");
    cameraUrl.append(this->camera->ip);
    cameraUrl.append(":");
    cameraUrl.append(std::to_string(this->camera->port));
    cameraUrl.append("/ISAPI/System/reboot");

#ifdef DEBUG
    std::cout << "cameraurl:" << cameraUrl << std::endl;
#endif // DEBUG
    curl_easy_setopt(curl, CURLOPT_URL, cameraUrl.c_str());
    curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT"); //根据要求，要用PUT方法，但为什么下面的POSTFIELDS会起作用，暂时还没清楚。记得libcurl的文档里说，POSTFIELDS所调用的是POST方法。。。待续。。。。
    res = curl_easy_perform(curl); //执行

    if (res != CURLE_OK)
    {
        fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
    }

    clearCurl(&curl, headers);
    return 0;
}

/******************************************************************************
 *  Name        :   HikCamera::factoryDefault
 *  Author      :   cqnews
 *  Version     :   V1.0.0
 *  Data        :   2020.09.21
 *  Describe    :   恢复出厂设置
 *  http://admin:hik123456@192.168.1.67:80//ISAPI/System/factoryDefault
 ******************************************************************************/
int HikCamera::factoryDefault()
{
    if (!isInit())
    {
        return -1;
    }

    CURLcode res;
    CURL* curl; //curl库
    struct curl_slist* headers = NULL;

    if (initCurl(&curl, headers) != 0)
    {
        clearCurl(&curl, headers);
        return -2;
    }

    //curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);// 改协议头
    std::string cameraUrl = "http://";
    cameraUrl.append(this->camera->username);
    cameraUrl.append(":");
    cameraUrl.append(this->camera->password);
    cameraUrl.append("@");
    cameraUrl.append(this->camera->ip);
    cameraUrl.append(":");
    cameraUrl.append(std::to_string(this->camera->port));
    cameraUrl.append("/ISAPI/System/factoryDefault");

#ifdef DEBUG
    std::cout << "cameraurl:" << cameraUrl << std::endl;
#endif //DEBUG

    curl_easy_setopt(curl, CURLOPT_URL, cameraUrl.c_str());
    curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "GET");
    res = curl_easy_perform(curl); //执行

    if (res != CURLE_OK)
    {
        fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
    }

    clearCurl(&curl, headers);
    return 0;

}

/******************************************************************************
 *  Name        :   HikCamera::initCurl
 *  Author      :   cqnews
 *  Version     :   V1.0.0
 *  Data        :   2020.09.21
 *  Describe    :   初始化Curl
 ******************************************************************************/
int HikCamera::initCurl(CURL** curl, struct curl_slist* headers)
{

    *curl = curl_easy_init(); //初始化

    if (curl == NULL)
    {
        return -1;
    }

    headers = curl_slist_append(headers, "Accept:text/html, application/xhtml+xml, */*");
    headers = curl_slist_append(headers, "Accept-Language:zh-CN");
    headers = curl_slist_append(headers, "User-Agent:Mozilla/5.0 (Windows NT 6.1;WOW64; Trident/7.0; rv:11.0) like Gecko");
    headers = curl_slist_append(headers, "Host:127.0.0.1");
    headers = curl_slist_append(headers, "Accept-Encoding: gzip, deflate");
    headers = curl_slist_append(headers, "Expect:"); //防止Expect：100-Continue
    headers = curl_slist_append(headers, "Content-Type:text/html; charset=utf-8");
    headers = curl_slist_append(headers, "Connection: Keep-Alive");

    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); //改协议头


    //设置超时时间
    curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 6); // set transport and time out time  
    curl_easy_setopt(curl, CURLOPT_TIMEOUT, 6);
    curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 50);
    curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, 5);
    curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
    curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);

    curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 1);

    return 0;
}

/******************************************************************************
 *  Name        :   HikCamera::clearCurl
 *  Author      :   cqnews
 *  Version     :   V1.0.0
 *  Data        :   2020.09.21
 *  Describe    :   清空Curl
 ******************************************************************************/
int HikCamera::clearCurl(CURL** curl, curl_slist* headers)
{
    if (curl != NULL) { curl_easy_cleanup(*curl); curl = NULL; }
    if (headers != NULL) { curl_slist_free_all(headers); curl = NULL; }

    return 0;
}



/// <summary>
/// 返回签名
/// </summary>
/// <returns></returns>
int HikCamera::generateAuthorization(IsapiProtocolCommand protocolCommand, const char* protocolUrl, char* authorizationBuf)
{
    try
    {

        if (!isInit())
        {
            return -1;
        }

        CURL* curl; //curl库
        struct curl_slist* headers = NULL;

        if (initCurl(&curl, headers) != 0)
        {
            clearCurl(&curl, headers);
            return -2;
        }

        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); //改协议头

        std::string cameraUrl = "http://";
        cameraUrl.append(this->camera->ip);
        cameraUrl.append(":");
        cameraUrl.append(std::to_string(this->camera->port));
        cameraUrl.append(protocolUrl);

        std::string readHeader = "";
        curl_easy_setopt(curl, CURLOPT_URL, cameraUrl.c_str());
        curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "GET");
        curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, readStringBuffFunction);
        curl_easy_setopt(curl, CURLOPT_HEADERDATA, &readHeader);

        CURLcode res;
        res = curl_easy_perform(curl); //执行

        if (res != 0)
        {
            fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
            clearCurl(&curl, headers);
            return -2;
        }

        clearCurl(&curl, headers);
        if (readHeader.length() <= 0)
        {
            return -3;
        }

        //std::cout << "readHeader:" << readHeader << std::endl;

        char realmChr[128] = { 0 };
        char nonceChr[128] = { 0 };

        int ret = -1;
        ret = (int)readStringKey(readHeader.c_str(), "realm", realmChr);
        if (ret <= 0) { return -1; }
        ret = (int)readStringKey(readHeader.c_str(), "nonce", nonceChr);
        if (ret <= 0) { return -1; }
#ifdef DEBUG
        std::cout << "realmChr:" << realmChr << std::endl;
        std::cout << "nonceChr:" << nonceChr << std::endl;
#endif // DEBUG

        char szSrc[HTTP_DEGIST_SRC_LEN] = { 0 };
        sprintf(szSrc, "%s:%s:%s", this->camera->username, realmChr, this->camera->password);
#ifdef DEBUG
        std::cout << "szHA1:" << szSrc << std::endl;
#endif // DEBUG

        char szHA1[HTTP_HA_LEN] = { 0 }; //与安全相关的数据的A1
        ret = Md5::md5(szSrc, min(HTTP_HA_LEN, strlen(szSrc)), szHA1);
        if (ret != 0) { return -1; }

        memset(szSrc, 0, HTTP_DEGIST_SRC_LEN);
        if (protocolCommand == ISAPI_GET)
        {
            sprintf(szSrc, "GET:%s", protocolUrl);
        }
        else if (protocolCommand == ISAPI_PUT)
        {
            sprintf(szSrc, "PUT:%s", protocolUrl);
        }
        else if (protocolCommand == ISAPI_POST)
        {
            sprintf(szSrc, "POST:%s", protocolUrl);
        }
        else
        {
            return -1;
        }
        char szHA2[HTTP_HA_LEN] = { 0 }; //与报文相关的数据的A2
        ret = Md5::md5(szSrc, min(HTTP_HA_LEN, strlen(szSrc)), szHA2);
#ifdef DEBUG
        std::cout << "szHA2:" << szSrc << std::endl;
#endif // DEBUG
        if (ret != 0) { return -1; }

        memset(szSrc, 0, HTTP_DEGIST_SRC_LEN);
        sprintf(szSrc, "%s:%s:%s", szHA1, nonceChr, szHA2);
#ifdef DEBUG
        std::cout << "szResponse:" << szSrc << std::endl;
#endif // DEBUG

        char szResponse[HTTP_HA_LEN] = { 0 }; //Authorization请求头里面的response内容    
        ret = Md5::md5(szSrc, strlen(szSrc), szResponse);
        if (ret != 0) { return -1; }

        const char* authorFormat = "Authorization: Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\",response=\"%s\"\r\n\r\n";
        sprintf(authorizationBuf, authorFormat, this->camera->username, realmChr,
            nonceChr, protocolUrl, szResponse);

#ifdef DEBUG
        std::cout << "authorizationBuf:" << authorizationBuf << ",ok" << std::endl;
#endif // DEBUG

        return 0;
    }
    catch (...)
    {

    }

    return -1;
}

/// <summary>
/// 获取key
/// </summary>
/// <param name="sourceChr"></param>
/// <param name="key"></param>
/// <param name="returnChr"></param>
/// <returns></returns>
size_t HikCamera::readStringKey(const char* sourceChr, const char* key, char* returnChr)
{
    try
    {
        if (sourceChr == NULL || key == NULL) { return -1; }

        std::string startChr = key;
        startChr.append("=\"");

        const char* vtmp = strstr(sourceChr, startChr.c_str());
        if (vtmp == NULL) { return -1; }
        vtmp += startChr.length();

        sscanf(vtmp, "%[^\"]", returnChr); //  如果匹配到字符，则字符后面的不在进行匹配	
        return strlen(returnChr);
    }
    catch (...)
    {

    }

    return -1;

}

/******************************************************************************
 *  Name        :   HikCamera::readStringBuffFunction
 *  Author      :   cqnews
 *  Version     :   V1.0.0
 *  Data        :   2020.09.21
 *  Describe    :   读取Curl消息方法
 ******************************************************************************/
size_t HikCamera::readStringBuffFunction(char* contents, size_t size, size_t nmemb, void* userStr)
{
    ((std::string*)userStr)->append((char*)contents, size * nmemb);
    return size * nmemb;
}



HikCamera* HikCamera::getInstance()
{
    if (m_instance_ptr == nullptr)
    {
        pthread_mutex_lock(&mutex);
        if (m_instance_ptr == nullptr)
        {
            m_instance_ptr = new HikCamera();
        }
        pthread_mutex_unlock(&mutex);
    }

    return m_instance_ptr;
}

pthread_mutex_t HikCamera::mutex;
HikCamera* HikCamera::m_instance_ptr = nullptr;